#include <linux/module.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/of.h>
#include <linux/slab.h>
#include <linux/of_address.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/interrupt.h>

#include <linux/wait.h>
#include <linux/ide.h>

#define IMX6UIRQ_NAME  "imx6uirq" /* 设备名字 */
#define IMX6UIRQ_COUNT  1    /* 设备号个数 */
#define KEY_NUM         1

#define KEY_0_VALUE     0x01
#define INVALKEY        0xFF
/* KEY 结构体 */
struct irq_keydesc{
    int gpio;            //io编号
    int irqnum;          //中断号
    unsigned char value; //键值
    char name[16];       //名称
    irqreturn_t (*handler) (int , void *); //中断处理函数

    struct tasklet_struct tasklet;
};

// imx6uirq 设备结构体
struct imx6uirq_dev {
    dev_t devid;
    int major;
    int minor;

    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *nd;

    struct irq_keydesc irqkey[KEY_NUM];

    struct timer_list timer;

    atomic_t key_value;
    atomic_t key_release;

    wait_queue_head_t r_wait;  //读等待队列头
};
struct imx6uirq_dev imx6uirq;

int imx6uirq_open (struct inode *node, struct file *filep)
{
    printk(" open !\r\n");
    filep->private_data = &imx6uirq;
    return 0;
}

ssize_t imx6uirq_read (struct file *filep, char __user *buf, size_t size, loff_t *offset)
{
    int ret = 0;

    unsigned char keyvalue;
    unsigned char releasekey;

    struct imx6uirq_dev *dev = filep->private_data;
#if 0
    // 等待事件
    wait_event_interruptible(dev->r_wait, atomic_read(&dev->key_release));  //等待按键有效
#endif

    DECLARE_WAITQUEUE(wait, current);  //定义一个等待队列项
    if(atomic_read(&dev->key_release) == 0){ //按键没有按下
        add_wait_queue(&dev->r_wait, &wait);  // 将队列项添加到等待队列头
        __set_current_state(TASK_INTERRUPTIBLE);  //当前进程设置为可被打断的状态
        schedule();
        // 唤醒以后从这里运行
        if(signal_pending(current)){
            ret = -ERESTARTSYS;
            goto data_error;
        }

        __set_current_state(TASK_RUNNING);  //将当前任务，设置为运行状态
        remove_wait_queue(&dev->r_wait, &wait);  //将对应的队列项从等待队列头删除
    }

    keyvalue = atomic_read(&dev->key_value);
    releasekey = atomic_read(&dev->key_release);

    if(releasekey){  //有效按键
        if(keyvalue & 0x80){
            keyvalue &= ~0x80;
            ret = __copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        }
        else{
            goto data_error;
        }
        atomic_set(&dev->key_release, 0);  // 按下标志清零
    }
    else{
        goto data_error;
    }

    return 0;
data_error:
    __set_current_state(TASK_RUNNING);  //将当前任务，设置为运行状态
    remove_wait_queue(&dev->r_wait, &wait);  //将对应的队列项从等待队列头删除
    return ret;
}

int imx6uirq_release (struct inode *node, struct file *filep)
{
    struct imx6uirq_dev *dev = filep->private_data;
    dev = NULL;
    printk(" release !\r\n");

    return 0;
}

static struct file_operations imx6uirq_ops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
    .release = imx6uirq_release,
};

// 按键中断处理函数
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    // int value = 0;
    struct imx6uirq_dev *dev = dev_id;
#if 0
    dev->timer.data = (volatile unsigned long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
#endif

    tasklet_schedule(&dev->irqkey[0].tasklet);

	return IRQ_HANDLED;
}

/* tasklet */
static void key_tasklet(unsigned long data)
{
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)data;

    // printk("key_tastlet\r\n");
    dev->timer.data = (volatile unsigned long)data;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
}

/* 定时器处理函数 */
static void timer_function(unsigned long arg)
{
    int value = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

    value = gpio_get_value(dev->irqkey[0].gpio);
    if(value == 1){  // 按下按键
        atomic_set(&dev->key_value, dev->irqkey[0].value);
    }
    else if(value == 0){  // 按下按键
        atomic_set(&dev->key_value, 0x80 | (dev->irqkey[0].value));
        atomic_set(&dev->key_release, 1);  //完整的按键过程
    }

    // 唤醒进程
    if(atomic_read(&dev->key_release)){
        wake_up(&dev->r_wait);
    }
}

static int keyio_init(struct imx6uirq_dev *dev)
{
    int ret = 0;
    int i = 0;
    /* 1、按键初始化 */
    /* 获取设备树节点 */
    dev->nd = of_find_node_by_path("/key");
    if(dev->nd == NULL) {
        printk("Can't find key node !\r\n");
        ret = -EIO;
        return ret;
    }

    for(i=0;i < KEY_NUM; i++)
    {
        /* 获取按键对应的 GPIO */
        dev->irqkey[i].gpio = of_get_named_gpio(dev->nd, "key-gpios", i);
        if(dev->irqkey[i].gpio < 0){
            printk("can't get key%d\r\n", i);
        }
        printk("key[%d] gpio num = %d\r\n", i, dev->irqkey[i].gpio);

        memset(dev->irqkey[i].name, 0, sizeof(dev->irqkey[i].name));
        sprintf(dev->irqkey[i].name, "key%d", i);
        gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name);
        gpio_direction_input(dev->irqkey[i].gpio);

        dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio);  // 获取中断号
    #if 0
        dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd, i);
    #endif
    }

    dev->irqkey[0].handler = key0_handler;
    dev->irqkey[0].value = KEY_0_VALUE;
    /* 2、按键中断初始化 */
    for(i=0; i < KEY_NUM; i++)
    {
        ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[i].handler,
                            IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,
                            dev->irqkey[i].name, &imx6uirq);
        if(ret < 0){
            printk("irq %d request failed!\r\n", dev->irqkey[i].irqnum);
            goto fail_irq;
        }

        tasklet_init(&dev->irqkey[i].tasklet, key_tasklet, (unsigned long)dev);
    }

    // 初始化定时器
    init_timer(&dev->timer);
    dev->timer.function = timer_function;

    return 0;

fail_irq:
    for(i=0; i < KEY_NUM; i++){
        gpio_free(dev->irqkey[i].gpio);
    }
    return ret;
}

static int __init imx6uirq_init(void)
{
    int ret = 0;
    printk(" init !\r\n");

    /* 1、注册设备号 */
    imx6uirq.major = 0;
    if(imx6uirq.major){
        ret = register_chrdev_region(imx6uirq.devid, IMX6UIRQ_COUNT, IMX6UIRQ_NAME);
    }
    else{
        ret = alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_COUNT, IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.devid);
    }
    if(ret < 0){
        goto fail_devid;
    }
    printk("imx6uirq.major = %d, imx6uirq.minor = %d\r\n", imx6uirq.major, imx6uirq.minor);

    cdev_init(&imx6uirq.cdev, &imx6uirq_ops);
    ret = cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_COUNT);
    if(ret < 0){
        goto fail_cdev;
    }

    imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.class)){
        ret = PTR_ERR(imx6uirq.class);
        goto fail_class;
    }

    imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL,IMX6UIRQ_NAME);
    if(IS_ERR(imx6uirq.device)){
        ret = PTR_ERR(imx6uirq.device);
        goto fail_device;
    }

    ret = keyio_init(&imx6uirq);
    if(ret < 0){
        goto fail_node;
    }

    // 初始化原子变量
    atomic_set(&imx6uirq.key_value, INVALKEY);
    atomic_set(&imx6uirq.key_release, 0);

    // 初始化等待队列头
    init_waitqueue_head(&imx6uirq.r_wait);

    return 0;

fail_node:
    device_destroy(imx6uirq.class, imx6uirq.devid);
fail_device:
    class_destroy(imx6uirq.class);
fail_class:
    cdev_del(&imx6uirq.cdev);
fail_cdev:
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_COUNT);
fail_devid:
    return ret;
}

static void __exit imx6uirq_exit(void)
{
    int i = 0;

    // 释放中断
    for(i=0; i < KEY_NUM; i++){
        free_irq(imx6uirq.irqkey[i].irqnum, &imx6uirq);
    }

    // 释放IO
    for(i=0; i< KEY_NUM; i++)
    {
        gpio_free(imx6uirq.irqkey[i].gpio);
    }
    // 删除定时器
    del_timer_sync(&imx6uirq.timer);
    // 注销字符设备
    device_destroy(imx6uirq.class, imx6uirq.devid);
    class_destroy(imx6uirq.class);
    cdev_del(&imx6uirq.cdev);
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_COUNT);

    printk(" exit !\r\n");
}


module_init(imx6uirq_init);
module_exit(imx6uirq_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("george <g_yubo@163.com>");